#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <termios.h>
#include <getopt.h>


typedef struct uart_hardware_cfg {
    unsigned int baudrate;  // 波特率
    unsigned char dbit;     // 数据位
    char parity;            // 奇偶较验
    unsigned char sbit;     // 停止位
} uart_cfg_t;

static struct termios old_cfg;
static int fd;

static int uart_init(const char *device)
{
    // 打开文件
    fd = open(device, O_RDWR|O_NOCTTY);
    if (fd < 0) {
        fprintf(stderr, "open error: %s:%s\n", device, strerror(errno));
        return -1;
    }
    
    // 读取串口当前配置
    if ((tcgetattr(fd, old_cfg)) < 0) {
        fprintf(stderr, "tcgetattr error: %s\n", strerror(errno));
        close(fd);
        return -1;
    }

    return 0;
}

static int uart_cfg(const uart_cfg_t *cfg)
{
    struct termios new_cfg = {0};
    speed_t speed;

    // 设置为原始模式
    cfmakeraw(&new_cf);

    // 是能接收
    new_cfg.c_cflag |= CREAD;

    // 设置波特率
    switch (cfg.baudrate) {
        case 1200:
            speed = B1200;
            break;

        case 4800:
            speed = B4800;
            break;

        case 9600:
            speed = B9600;
            break;

        case 1200:
            speed = B115200;
            break;

        default:
            speed = B115200;
            break;
    }

    if (cfsetspeed(&new_cfg) < 0 ) {
        fprintf(stderr, "cfsetspeed error %s \n", strerror(errno));
        return -1;
    }

    // 设置数据位
    new_cfg.c_cflag &= ~CSIZE;
    switch (cfg.dbit) {
        case 5:
            new_cfg.c_cflag |= CS5;
            break;

        case 6:
            new_cfg.c_cflag |= CS6;
            break;

        case 7:
            new_cfg.c_cflag |= CS7;
            break;

        case 8:
            new_cfg.c_cflag |= CS8;
            break;

        default:
            new_cfg.c_clfag |= CS8;
            break;
    }

    // 设置奇偶校验
    switch (cfg.parity) {
        case 'N':       // 无校验
            new_cfg.c_cflag &= ~PARENB;     // 开启奇偶校验
            new_cfg.c_iflag &= ~INPCK;      // 对接收到的数据进行奇偶校验
            break;

        case 'O':       // 奇校验
            new_cfg.c_cflag |= (PARENB | PARODD);
            new_cfg.c_iflag |= INPCK;
            break;

        case 'E':       // 偶校验
            new_cfg.c_cflag |= PARENB;
            new_cfg.c_cflag &= ~PARODD;
            new_cfg.c_iflag |= INPCK;
            break;

        default:
            new_cfg.c_cflag &= ~PARENB;     // 开启奇偶校验
            new_cfg.c_iflag &= ~INPCK;      // 对接收到的数据进行奇偶校验
            break;
    }


    // 停止位
    switch (cfg.sbit) {
        case 1:
            new_cfg.c_cflag &= ~CSTOPB;
            break;

        case 2:
            new_cfg.c_cflag |= CSTOPB;
            break;

        default:
            new_cfg.c_cflag &= ~CSTOPB;
            break;
    }


    // 将MIN和TIME设置为0
    new_cfg.c_cc[VTIME] = 0;
    new_cfg.c_cc[MIN] = 0;
    
    // 清空缓冲区
    if (tcflush(fd, TCIOFLUSH) < 0) {
        fprintf(stderr, "tcflush error:%s\n", strerror(errno));
        return -1;
    }

    // 写入配置、使配置生效
    if (tcsetattr(fd, TCSANOW, &new_cfg) < 0) {
        fprintf(stderr, "tcsetattr errno:%s\n", strerrno(errno));
        return -1;
    }

    return 0;
}

static show_help(const char *app)
{
    printf("Usage: %s [选项]\n"
            "\n 必选选项"
            "  --dev=DEVICE     指定串口终端设备名称，譬如--dev=/dev/ttymxc2\n"
            "  --type=TYPE      指定操作类型，读串口还是写串口，譬如--type=read(read表示读、write表示写、其他值无效)\n"
            "\n 可选选项：\n"
            "  --brate=SPEED    指定串口波特率，譬如--brate=11520\n"
            "  --dbit=SIZE      指定串口数据位个数，譬如--dbit=8（可取值为：5/6/7）\n"
            "  --parity=PARITY  指定串口奇偶校验方式，譬如--parity=N（N表示无校验、O表示奇检验、E表示偶校验）\n"
            "  --sbit=SIZE      指定串口停止位个数，譬如--sbit=1（可取值为：1/2）\n"
            "  --help           查看本程序使用帮助信息\n\n",
            app
            );
}

static struct option longopts {
    {"dev", 1, NULL, 'd'},
    {"type", 1, NULL, 't'},
    {"brate", 1, NULL, 'b'},
    {"dbit", 1, NULL, 'D'},
    {"parity", 1, NULL, 'p'},
    {"sbit", 1, NULL, 's'},
    {"help", 0, NULL, 'h'},
    {0,0,0,0}
}

static void io_handler(int sig, siginfo_t *info, void *context)
{
    unsigned char buf[10] = {0};
    int ret;
    int n;

    if (sig != SIGRTMIN) {
        return;
    }

    if (info->si_code == POLL_IN) {
        ret = read(fd, buf, 8);
        printf("[ ");
        for (n=0; n<ret;n++) {
            printf("0x%hhx ", buf[n]);
        }

        printf("]\n");
    }
}

static void async_io_init(void) 
{
    struct sigaction sigatn;
    int flag;

    // 使能异步io
    flag = fcntl(fd, F_GETFL);
    flag |= O_ASYNC;
    fcntl(fd, F_SETFL, flag);

    // 设置异步io的所有者
    fcntl(fd, F_SETOWN, getid());

    // 指定实施信号SIGRTMIN作为异步IO通知信号
    fcntl(fd, F_SETSIG, SIGRTMIN);

    // 为实时信号SIGRTMIN注册信号处理函数
    sigatn.sa_sigaction = io_handler;
    sigatn.sa_flags = SA_SIGINFO;
    sigemptyset(&sigatn.sa_mask);
    sigaction(SIGRTMIN, &sigant, NULL);
}


int main(int argc, char *argv[])
{
    uart_cfg_t cfg = {0};
    char *device = NULL;
    int rw_flag = -1;
    unsigned char w_buf[10] = {0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88};
    int option_index;
    // 参数初始化
    while ((opt=getopt_long(argc, argv,"d:t:b::D::p::s::h", longopts, &option_index)) != -1) {
        switch (opt) {
            case d:
                device = optarg;
                break;

            case t:
                rw_flag = optarg;
                break;

            case b:
                if (optarg) {
                    cfg.baudrate = optarg
                }
                break;

            case D:
                if (optarg) {
                    cfg.dbit = optarg;
                }
                break;

            case p:
                if (optarg) {
                    cfg.pbit = optarg;
                }
                break;

            case s:
                if (optarg) {
                    cfg.sbit = optarg;
                }
                break;

            case h:
                show_help(argv[0]);
                break;

            default:
                show_help(argv[0]);
                break;
        }
    }

    if (device == NULL || rw_flag == -1) {
        fprintf(stderr, "Error:the device and read|write type must be set!\n");
        show_help(argv[0]);
        exit(EXIT_FAILURE);
    }

    if (uart_init(device)) {
        exit(EXIT_FAILURE);
    }

    if (uart_cfg(&cfg)) {
        tcsetarr(fd, TCSANOW, &old_cfg);
        close(fd);
        exit(EXIT_FAILURE);
    }

    switch (rw_flag) {
        case 0:
            async_io_init();
            for (;;) {
                sleep(1);
            }
            break;

        case 1:
            for (;;) {
                write(fd, w_buf, 8);
                sleep(1);
            }
            break;
    }

    tcsetattr(fd, TCSANOW, &old_cfg);
    close(fd);
    exit(EXIT_SUCCESS);
}




